import os
import sys
import re
import sqlite3
import configparser
from datetime import date

from time import time

from PyQt5.QtWebKitWidgets import *
from PyQt5.QtNetwork import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *


# sys.path.append('c:\PyLib')
# from WebKit import *



# logging.basicConfig(filename='lib.log',level=logging.DEBUG,
    # format = u'%(filename)s[LINE:%(lineno)d]# %(levelname)-8s %(message)s')


class Browser(QWebView):
    """
    виджет браузера
    """
    def __init__(self):
        QWebView.__init__(self)
        self.Сookies = QNetworkCookieJar() # куки
        self.page().networkAccessManager().setCookieJar(self.Сookies)
        
    def current_html(self):
        return self.page().mainFrame().toHtml()

    def get(self, url=None):
        # line.setText('{}'.format(url))
        t1 = time()
        if 1:
            loop = QEventLoop()
            timer = QTimer()
            timer.setSingleShot(True)
            timer.timeout.connect(loop.quit)
            self.loadFinished.connect(loop.quit)
            self.load(QUrl(url))
            timeout = 10
            timer.start(timeout * 4000)
            loop.exec_()                                                        # delay here until download finished or timeout

            if timer.isActive():
                timer.stop()                                                    # downloaded successfully
            else:
                print('Выход по таймеру')
            self.html = self.current_html()
    

class FormSpider(QWidget):

    """ """

    def __init__(self, name):
        super().__init__()

        self.name = name
        self.create_environment()
        self.create_ui()
        self.data_link()


    def create_environment(self):

        """ СОЗДАНИЕ РАБОЧЕЙ СРЕДЫ.
        --- Создание рабочей папки в 'APPDATA'.
        --- Пути до файлов: куки, БД, ini.
        --- Файл ini или в папке 'APPDATA', или в текущей.
        --- Создание файла конфигурации при отсутствии.
        --- Создаем БД с таблицами."""

        self.dir_work = "{}\\{}".format(os.getenv('APPDATA'), self.name)
        if not os.path.exists(self.dir_work): os.makedirs(self.dir_work)
        
        self.file_cookeies = self.dir_work + '\\' + 'cookies.txt'
        self.file_db = self.dir_work + '\\' + self.name + '.db'
        file_ini = self.dir_work + '\\' + self.name + '.ini'
        self.file_log = self.dir_work + '\\' + 'log.txt'

        if not os.path.exists(file_ini): 
            f_out = open(file_ini_local,'w')
            f_out.write("[BASIC]\nout_dir = out_dir")
            f_out.close()
        
        config = configparser.ConfigParser()
        config.read(file_ini)
        self.out_dir = config['BASIC'].get('out_dir','out')

        self.conn = sqlite3.connect(self.file_db, isolation_level = None)
        query = self.conn.cursor()
        query.execute("CREATE TABLE IF NOT EXISTS vacant ("
            "url TEXT DEFAULT '',"
            "org TEXT DEFAULT '',"
            "face TEXT DEFAULT '',"
            "phone TEXT DEFAULT '',"
            "phone_find TEXT DEFAULT '',"
            "org_CRM TEXT DEFAULT '',"
            "curator TEXT DEFAULT '');")
        query.execute("CREATE TABLE IF NOT EXISTS ref "
            "(date TEXT, url TEXT DEFAULT '');")
        query.execute("CREATE TABLE IF NOT EXISTS org "
            "(name TEXT DEFAULT '');")


    def create_ui(self):

        """ СОЗДАНИЕ ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА """
        """-- Кнопки помещаем в горизотальный бокс с затычкой справа.
        -- В вертикальный бокс помещаем бокс с кнопками, URL и броузер."""


        btn_run = QPushButton("СОБРАТЬ ВАКАНСИИ")
        btn_run.setStyleSheet('font: bold; color: #0000FF')
        btn_run.clicked.connect(self.full_cycle)
        btn_org = QPushButton("Блокировка организаций")
        btn_org.clicked.connect(self.org)

        self.functions = QComboBox()
        self.functions.currentIndexChanged.connect(self.functions_changed)

        self.btn_execute = QPushButton("выполнить")
        self.btn_execute.clicked.connect(self.execute)

        self.line = QLineEdit()
        self.web = Browser()
        
        self.pb = QProgressBar()

        # СБОРКА -------------------------------------------------------------
        hbox = QHBoxLayout()
        hbox.addWidget(btn_run)
        hbox.addStretch()
        hbox.addWidget(btn_org)
        hbox.addStretch()
        hbox.addWidget(self.functions)
        hbox.addWidget(self.btn_execute)
        

        vbox = QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addWidget(self.line)
        vbox.addWidget(self.web)
        vbox.addWidget(self.pb)

        self.setLayout(vbox)
        self.setWindowTitle(self.name)
        self.setWindowIcon(QIcon('pict\\main.ico'))
        self.resize(1200, 700)
        self.show()


    def data_link(self):

        """ ЗАПОЛНЕНИЕ ПОЛЕЙ ФОРМЫ.
        -- Создаём словарь функций программы. Создаём модель для поля <функции>.
        """

        self.D = {
            ' 1.Загрузить куки' : 'self.cookies_load()',
            ' 2.Логин ' : 'login()',
            ' 3.Собрать ссылки ' : 'collect_ref()',
            ' 4.Удалить старые ссылки ' : 'self.del_old_ref()',
            ' 5.Добавить ссылки в архив ' : 'self.add_ref()',
            ' 6.Загрузить вакансии ' : 'load_vacant()',
            ' 7.Удаляем дубли и баним ' : 'self.clean_org()',
            ' 8.Проверка по CRM ' : 'self.find_CRM()',
            ' 9.Вывод ' : 'self.out()',
            '10.Сохранить куки' : 'self.cookies_save()'
            }
        l = list(self.D.keys())
        l.sort()
        self.model_functions = QStringListModel(l, self)
        self.functions.setModel(self.model_functions)


    def execute(self):

        """ ПОШАГАВОЕ ВЫПОЛЕНИЕ ОПЕРАЦИЙ.
        -- Выполняем все команды из массива full_cycle."""

        from __main__ import collect_ref, load_vacant, login
        exec(self.D[self.functions.currentText()])
        self.btn_execute.setEnabled(False)


    def full_cycle(self):

        """ ПОЛНЫЙ ЦИКЛ РАБОТЫ ПАУКА.
        -- Выполняем все команды из массива full_cycle."""

        from __main__ import collect_ref, load_vacant, all_cycle
        for step in all_cycle:
            print(step)
            exec(step)


    def functions_changed(self,index):

        self.btn_execute.setEnabled(True)


    def cookies_load(self):

        """ ЗАГРУЖАЕМ КУКИ.
        -- Куки лежат в рабочей папке. Если файл с куками есть, то загружаем."""

        if os.path.exists(self.file_cookeies):
            f = open(self.file_cookeies, 'r')
            allC = tuple(cooky for li in f
                for cooky in QNetworkCookie.parseCookies(li.rstrip()))
            if allC:
                self.web.Сookies.setAllCookies(allC)
            f.close()


    def cookies_save(self):

        """ СОХРАНЯЕМ КУКИ.
        -- Куки лежат в папке cookies."""

        f = open(self.file_cookeies, 'wb')
        allC = self.web.Сookies.allCookies()
        for c in allC:
            f.write(c.toRawForm()+'\r\n')
        f.close()



    def del_old_ref(self):

        """ УДАЛЯЕМ ВЧЕРАШНИЕ ССЫЛКИ.
        -- В запросе условие (AND '{}' > r.date;) необходимо, чтобы при повторном запуске
        выдавались сегодняшние вакансии, а не вакансии между запусками. Это защита от
        случайного повторного запуска программы.
        """
        
        now = date.today().strftime('%Y-%m-%d')

        query = self.conn.cursor()                                                       # курсор SQLite3

        sql = ("DELETE FROM vacant WHERE ROWID IN "                                 # удалить из ссылки сохраненные в ref
            "(SELECT v.ROWID FROM vacant as v, ref as r "                           # если дата архивной ссылки старая
            "WHERE (v.url= r.url) AND ('{}' > r.date));".format(now))

        try:
            query.execute(sql)                                                      # удаляем ссылки
        except sqlite3.Error as e:
            logging.critical('sqlite: {} {}'.format( e.args[0],sql))
            QMessageBox.critical(None, "КРИТИЧЕСКАЯ ОШИБКА", "Подробности в log.txt.")
            quit()

        query.close()


    def add_ref(self):

        """
        Ссылки на вакансии из рабочей таблицы добавляются в архив ссылок.
        Записываем URL и текущую дату для контроля.
        """
        now = date.today().strftime('%Y-%m-%d')

        query = self.conn.cursor()                                                       # курсор SQLite3

        sql = ("INSERT INTO ref (date, url) SELECT '{}', url FROM vacant;".         # сливаем ссылки в таблицу ref
            format(now))
        try:
            query.execute(sql)
        except sqlite3.Error as e:
            logging.critical('sqlite: {} {}'.format( e.args[0],sql))
            QMessageBox.critical(None, "КРИТИЧЕСКАЯ ОШИБКА", "Подробности в log.txt.")
            quit()

        query.close()


    def clean_org(self):

        """ УДАЛЕНИЕ ВАКАНСИЙ.
        -- Без названия организации или без телефона.
        -- С одинаковыми названиями организаций.
        -- С одинаковыми телефонами.
        -- Забаненых организаций."""

        query = self.conn.cursor()

        query.execute("DELETE FROM vacant WHERE (phone = '') OR (org = '');")

        query.execute("DELETE FROM vacant WHERE ROWID NOT IN ("
            "SELECT MIN(ROWID) FROM vacant GROUP BY org);")

        query.execute("DELETE FROM vacant WHERE ROWID NOT IN ("
            "SELECT MIN(ROWID) FROM vacant GROUP BY phone);")

        query.execute("DELETE FROM vacant WHERE ROWID IN "
            "(SELECT v.ROWID FROM vacant as v, org as r WHERE v.org=r.name);")

        query.close()


    def find_CRM(self):
        """ ПРОВОВЕРКА ПО CRM (соединение с SQLite)
        -- Соединение базами данных.
        -- Читаем последовательно таблицу. Нормализуем телефоны.
        -- Формируем по нормализованным телефонам запрос и ищем в ЛЕКСКМЕ.
        --
        --
        """

        def phone_norm(phones):
            """
            Нормализация телефонов.
            Сотовые до 10 цифр.
            Самарские стационарные до 7 цифр.
            Тольяттинские стационарные до 6 цифр.
            """
            phones = ''.join(filter(lambda x: x.isalnum() or x == ',', phones))         # оставляем буквы, цыфры, запятые
            phones = phones.strip(',').split(',')                                       # убрать последнюю запятую и создать список

            for i, phone in enumerate(phones):
                p = phone.find('до')                                                    # убрать добавочный номер
                if p != -1:                                                             # всё после слов дополниельный или добавочный
                    phone = phone[:p]
                if len(phone)>=11:                                                      # убрать код России - урезать до 10 цифр
                    phone = phone[1:]
                if phone[:3]== '846':                                                   # убрать код Самары
                    phone = phone[3:]
                if phone[:4]== '8482':                                                  # убрать код Тольятти
                    phone = phone[3:]
                phones[i] = phone
            phones.sort()
            return(','.join(phones))# возвращаем сортированный список телефонов

        dbMS = QSqlDatabase.addDatabase("QODBC")                                    # соедиение с лексемой
        dbMS.setDatabaseName("lexema")
        dbMS.setUserName("sa")
        dbMS.setPassword("1953")
        if dbMS.open():
            print("соединение lexema")

        lexema = QSqlQuery()
        update = self.conn.cursor()                                                      # для обновления записей
        select = self.conn.cursor()                                                      # для обхода записей
        select.execute("SELECT ROWID, phone, phone_find, org_crm, curator FROM vacant;")                     # читать телефоны

        for rowid, phone, phone_find, org_crm, curator in select:
            phone_find = phone_norm(phone)                                     # нормализация телефонов

            phones = phone_find.split(',')  # список телефонов
            where = ["Phone LIKE '%{}%'".format(phone) for phone in phones]
            where = ' OR '.join(where)
            sql = ("SELECT s.fullname, u.name FROM dbo.Spr_Org s "              # запрос на поиск в телефонам
                   "INNER JOIN dbo.Unianalit u ON u.vcode = s.kurator WHERE {}"
                   .format(where))

            # logging.debug('find_CRM: {}'.format(sql))

            lexema.exec(sql)

            if lexema.next():                                                   # первая попавшаяся запись
                curator = re.match('[^ ]*',lexema.value(1)).group()             # отбросить имя и отчество куратора
                org_crm = "({}){}".format(curator, lexema.value(0).rstrip())    # название организации в лексеме

            sql = ("UPDATE vacant SET phone_find = ?, org_crm = ?, curator = ? "
                   "WHERE ROWID = {}".format(rowid))
            try:
                update.execute(sql,(phone_find, org_crm, curator))
            except sqlite3.Error as e:
                logging.critical('sqlite: {} {}'.format( e.args[0],sql))
                QMessageBox.critical(None, "КРИТИЧЕСКАЯ ОШИБКА", "Подробности в log.txt.")
                quit()

        update.close()
        select.close()


    def out(self):

        """ ВЫВОД (указатель на форму)
        -- Меняем всех пустых кураторов на Супервайзера.
        -- Собираем всех кураторов из базы и делаем из них список.
        -- Итерация спискау кураторов.
        -- Вывод csv по каждому менеджеру. """

        if not os.path.exists(self.out_dir):
            os.makedirs(self.out_dir)
        
        query = self.conn.cursor()
        query.execute("UPDATE vacant SET curator = 'Супервайзер' WHERE curator = ''")

        query.execute("SELECT curator FROM vacant GROUP BY curator;")
        curators = list(query)

        now = date.today().isoformat()                                         

        for curator, in curators:
            out_file = "{}\\{} {}.csv".format(self.out_dir, now, curator)
            print(out_file)
            f = open(out_file, 'w')
            query.execute("SELECT org, face, phone, phone_find, org_CRM,"
                "url, curator "
                "FROM vacant WHERE curator = '{}'".format(curator))
            for org, face, phone, phone_find, org_CRM, url, curator in query:
                s = ('{}${}${}$>{}<${}$=HYPERLINK("{}")${}\n'.
                format(org, face, phone, phone_find, org_CRM, url, curator))
                f.write(s)
            f.close()

        query.close()
        
        
    def org(self):
        dialog = TableOrg(self)
    
        
class TableOrg(QDialog): 

    """ ОРГАНИЗАЦИИ ДЛЯ БАНА """

    def __init__(self, parent=None):
        super().__init__(parent)

        self.file_db = parent.file_db
        self.create_ui()
        self.data_link()

    def create_ui(self):

        self.org = QTableView()
        # self.org.setFixedSize(200,50)
        # self.org.doubleClicked.connect(self.children_double_clik)
        self.org.setContextMenuPolicy(Qt.CustomContextMenu)
        self.org.customContextMenuRequested.connect(self.org_menu)
        
        # СБОРКА -------------------------------------------------------------
        v = QVBoxLayout()
        v.addWidget(self.org)
        
        self.setLayout(v)
        
        self.setWindowTitle('ОРГАНИЗАЦИИ ДЛЯ БЛОКИРОВКИ')
        self.setMinimumWidth(350)
##        self.setMaximumHeight(475)
        self.setWindowModality(1)
        self.show()
        
        
    def data_link(self):

        dbMS = QSqlDatabase.addDatabase("QSQLITE")                
        dbMS.setDatabaseName(self.file_db)
        if dbMS.open():
            print("соединение")

        self.model_org = QSqlTableModel()
        self.model_org.setTable('org')
        self.model_org.select()
        self.org.setModel(self.model_org)
        self.org.resizeColumnsToContents()
        
        """ ЗАГОЛОВКИ ТАБЛИЦЫ """
        self.model_org.setHeaderData(0, 1, 'Организация')
        self.org.setColumnWidth(0, 300)
    def org_menu(self, pos):
    
        #
        """ КОНТЕКСТНОЕ МЕНЮ. Создаём и запускаем"""
        menu = QMenu(self)
        menu.addAction("Добавить пустую строку", self.org_add)
        menu.exec_(QCursor.pos())

    def org_add(self):
    
        query = QSqlQuery()
        # print('yfpdfybt')
        query.exec("INSERT INTO org (name) VALUES ('Пусто');")
        # if query.lastError().isValid ():
            # print('{}'.format(query.lastError().text()))
        
        self.model_org.select()
